//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#ifndef NJ_EVENT_H
#	define NJ_EVENT_H

#	include "NjNinjaSyncLib.h"
#	include "NjLocker.h"
#	include "NjEventsSystem.h"
#	include "NjEvent_config.h"

	/// \brief Implements a manual reset event
	class NjEvent
	{
	public:
		/// \brief Defines the timeout type
		typedef NjEventsSystem::TimeMs TimeMs;

		/// \brief Creates a new NjEvent instance
		///
		/// \param bSet If is set to NJ_TRUE the event is created in not-blocking state. If is set to NJ_FALSE the event is created in blocking state
		NjEvent(NjBool bSet);

		/// \brief Destroys this NjEvent instance
		~NjEvent();

		/// \brief Sets the event into not-blocking state
		///
		/// This function can be also called when the NjEvent is already in not-blocking state.
		/// In this case nothing will happen
		void Set();

		/// \brief Sets the event into blocking state
		///
		/// This function can be also called when the NjEvent is already in blocking state.
		/// In this case nothing will happen
		void Unset();

		/// \brief Waits indefinitely the NjEvent to be set into not-blocking state
		void Wait();

		/// \brief Waits for \em uTimeMs milliseconds the NjEvent to be set into not-blocking state
		///
		/// If the event is set to non-blocking state before \em uTimeMs expires, it will return NJ_TRUE.
		/// If the event is still in blocking state after \em uTimeMs expires, it will return NJ_FALSE. 
		/// TryWait(0) will return immediately NJ_TRUE or NJ_FALSE according to the event state.
		/// TryWait(NjEventsSystem::WAIT_FOREVER) is identical to Wait
		/// \param uTimeMs The maximum time to wait in milliseconds
		NjBool TryWait(TimeMs uTimeMs);

	private:
		/// \brief The kernel event type
		typedef NjEventsSystem::Event Event;

		/// \brief Used to store the state of the NjEvent
		///
		/// This state matches the state of the kernel event when there are threads waiting on it
		static const NjUint STATE_USER_EVENT_UNSET   = 0x00000001;

		/// \brief Used to store the state of the kernel event object
		///
		/// This bit can be set only if the kernel object is allocated.
		/// When this bit is set it is expected that NjEventsSystem::TryWaitEvent operations will block the calling thread
		static const NjUint STATE_KERNEL_EVENT_UNSET = 0x00000002;

		/// \brief A tag counter used to avoid the ABA problem
		/// \sa http://en.wikipedia.org/wiki/ABA_problem
		static const NjUint STATE_ABA_COUNTER_ONE    = 0x00000004;

		/// \brief The current state of the event
		NjAtomic<NjUint> m_kState;

		/// \brief The kernel event assigned to this event
		NjAtomic<Event *> m_kEvent;

#	if NJ_EVENT_STORE_USERS_ENABLED
		/// \brief Called immediately entering into a method
		void UserEnter();

		/// \brief Called before leaving a method
		void UserLeave();

		/// \brief This counter stores the current threads accessing to this resource
		NjAtomic<NjUint> m_kUsers;
#	endif // NJ_EVENT_STORE_USERS_ENABLED

#	if NJ_EVENT_TRACKING_ENABLED
		/// \brief Class to track events usage
		class TrackerEntry;

		/// \copydoc TrackerEntry
		class Tracker;
#	endif // NJ_EVENT_TRACKING_ENABLED

		NJ_DISABLE_DEFAULT_CONSTRUCTOR(NjEvent);
		NJ_DISABLE_COPY(NjEvent);
	};

#	include "NjEvent_inline.h"

#endif // NJ_EVENT_H
